package control

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/avast/retry-go"
	match "github.com/dongweifly/sensitive-words-match"
	"github.com/golang-jwt/jwt/v4"
	"github.com/gorilla/websocket"
	"github.com/labstack/echo/v4"
	gogpt "github.com/sashabaranov/go-openai"
	"github.com/zxysilent/utils"

	"github.com/Meonako/webui-api"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
	"turingApi/conf"
	"turingApi/global"
	"turingApi/impl"
	"turingApi/model"
)

var (
	upgrader = websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	DictDir      = flag.String("d", "./data", "sensitive words file path")
	MatchService = match.NewMatchService()
)

const (
	code2sessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
)

type Template struct {
	templates *template.Template
}

type LoginResult struct {
	Data    string `json:"data"`
	Success bool   `json:"success"`
}

type RandomData struct {
	Data string `json:"data"`
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func WsHandlerV2(c echo.Context) error {
	var (
		wsConn *websocket.Conn
		err    error
		conn   *impl.Connection
		data   []byte
		openid string
		jcc    *JwtCustomClaims
	)
	ipt := &struct {
		Stream bool `query:"stream" json:"stream"`
	}{}
	err = c.Bind(ipt)
	if err != nil {
		return c.JSON(utils.Fail("Illegal arguments", err.Error()))
	}

	if jcc, err = LoadUser(c); err != nil {
		conf.Logger.Error(fmt.Sprintf("Load user failed: %v", err.Error()))
		return c.JSON(utils.Fail("Load user failed", err.Error()))
	}

	conf.Logger.Info(fmt.Sprintf("Loaded user expire at %v, openid %v, still valid? %v", jcc.ExpiresAt, jcc.OpenId, jcc.VerifyExpiresAt(time.Now(), false)))

	if c.Get("user") != nil {
		user := c.Get("user").(*jwt.Token)
		claims := user.Claims.(*JwtCustomClaims)
		openid = claims.OpenId
		conf.Logger.Info(fmt.Sprintf("Authentication successful for %s client ip %s", openid, c.RealIP()))
	}

	if wsConn, err = upgrader.Upgrade(c.Response().Writer, c.Request(), nil); err != nil {
		conf.Logger.Error(fmt.Sprintf("Upgrade failed %s", err))
		return err
	}
	if conn, err = impl.InitConnection(wsConn, c); err != nil {
		conf.Logger.Error(fmt.Sprintf("Init connection failed %s", err))
		goto ERR
	}

	// send user info message
	go func() {
		uMod, _ := model.UserGetByOpenid(openid)
		out, _ := json.Marshal(uMod)
		if err := conn.WriteMessage([]byte("userinfo|" + string(out))); err != nil {
			conf.Logger.Error(fmt.Sprintf("Write message (userinfo) failed %s", err))
			return
		}
	}()

	go func() {
		var err error
		for {
			if err = conn.WriteMessage([]byte("ping")); err != nil {
				conf.Logger.Error(fmt.Sprintf("Send ping msg failed %s, openid %s, client ip %s", err, openid, c.RealIP()))
				conn.Close()
				return
			}
			time.Sleep(60 * time.Second)
		}
	}()

	go func() {
		var err error
		var notice []model.Notice

		for {
			var noticeStr = "notice|"
			notice, err = model.NoticeAll()
			if err != nil {
				conf.Logger.Error(fmt.Sprintf("获取公告错误: %v", err))
				return
			}
			if len(notice) == 0 {
				noticeStr += "欢迎使用本程序，如遇故障请添加客服微信 rpcbind 进行反馈！"
			}
			for _, n := range notice {
				//conf.Logger.Info(fmt.Sprintf("notice: %v", n))
				noticeStr += n.Text + "; "
			}

			if err = conn.WriteMessage([]byte(noticeStr)); err != nil {
				conf.Logger.Error(fmt.Sprintf("Send notice msg failed %s, openid %s, client ip %s", err, openid, c.RealIP()))
				conn.Close()
				return
			}
			time.Sleep(120 * time.Second)
		}
	}()

	for {

		if data, err = conn.ReadMessage(); err != nil {
			conf.Logger.Error(fmt.Sprintf("Read message failed %s, openid %s, client ip %s", err, openid, c.RealIP()))
			goto ERR
		}
		var (
			prompt = string(data)
			now    = time.Now().Format("2006-01-02 15:04:05")
		)

		uMod, has := model.UserGetByOpenid(openid)
		if !has {
			if err := SendMsgAndEndConversation(conn, "非法请求"); err != nil {
				conf.Logger.Error(fmt.Sprintf("Illegal request"))
				goto ERR
			}
		}
		conf.Logger.Info(fmt.Sprintf("Balance of user %s => %d", uMod.Openid, uMod.Balance))

		switch prompt {
		case "hello":
			conf.Logger.Info(fmt.Sprintf("Client %s connected and say hello", uMod.Openid))
		case "getUserinfo":
			out, err := json.Marshal(uMod)
			if err != nil {
				conf.Logger.Error(fmt.Sprintf("Marshal error: %v", err))
			}
			err = conn.WriteMessage([]byte("userinfo|" + string(out)))
			if err != nil {
				conf.Logger.Info(fmt.Sprintf("Write message failed %s, openid %s, client ip %s", err, openid, c.RealIP()))
				goto ERR
			}

		default:
			msgInsufficientBalance := fmt.Sprintf(`抱歉，您（uid: %d）剩余提问积分已不足，请通过以下方式获取更多积分！

1、分享小程序，可获1分
2、去“更多”页面签到，可获3分
3、上报程序Bug可获奖励分
4、联系客服咨询更多获取途径

客服微信①：rpcbind
客服微信②：txpool`, uMod.Id)
			if uMod.Balance < 1 {
				conf.Logger.Error(msgInsufficientBalance)
				err = conn.WriteStruct(global.ChatMessage{Message: msgInsufficientBalance, Date: time.Now().String(), Type: 0})
				if err != nil {
					conf.Logger.Info(fmt.Sprintf("Error write sturct message(no enough balance) %s, openid %s, client ip %s", err, openid, c.RealIP()))
					goto ERR
				}
				continue
			}

			sensitiveWords, desensitization := MatchService.Match(prompt, '*')

			if len(sensitiveWords) > 0 {
				conf.Logger.Info(fmt.Sprintf("Sensitive words detected %s", sensitiveWords))
				prompt = desensitization
				err = conn.WriteStruct(global.ChatMessage{Message: fmt.Sprintf("您的提问含有敏感词：%s", strings.Join(sensitiveWords, ",")), Date: now, Type: 0})
				if err != nil {
					conf.Logger.Info(fmt.Sprintf("Write struct message (sensitiveWords) failed %s, openid %s, client ip %s", err, openid, c.RealIP()))
					goto ERR
				}
				continue
			}
			conf.Logger.Info(fmt.Sprintf("用户 %s 提问: %s, real ip %s, is stream? %v ", uMod.Openid, prompt, c.RealIP(), ipt.Stream))

			err := AiRequest(prompt, uMod, conn, ipt.Stream)
			if err != nil {
				conf.Logger.Info(fmt.Sprintf("Ai request failed %s", err))
				err = conn.WriteStruct(global.ChatMessage{Message: fmt.Sprintf("异常：%s", err), Date: now, Type: 0})
				if err != nil {
					conf.Logger.Info(fmt.Sprintf("Write struct message (exception) failed %s", err))
					goto ERR
				}
			}

		}
	}

ERR:
	conn.Close()
	return nil
}

func RandomHandler(c echo.Context) error {
	return c.JSON(http.StatusOK, RandomData{Data: "ChatGPT"})
}

func DeductBalanceSaveText(u *model.User, prompt string, text string) error {
	u.Balance -= 1
	u.Ltime = time.Now()
	if err := model.UserEdit(u, "balance", "ltime"); err != nil {
		conf.Logger.Error(fmt.Sprintf("User edit failed %s", err))
		return err
	}
	cMod := model.Conversation{Prompt: prompt, Completion: text, Created: time.Now(), UserId: u.Id, User: u}
	if err := model.ConversationAdd(&cMod); err != nil {
		conf.Logger.Error(fmt.Sprintf("Add conversation failed %s", err))
		return err
	}
	return nil
}

func SendMsgAndEndConversation(conn *impl.Connection, text string) error {
	var err error
	var now = time.Now().Format("2006-01-02 15:04:05")
	if len(text) > 0 {
		err = conn.WriteStruct(global.ChatMessage{Message: text, Date: now, Type: 0})
		if err != nil {
			conf.Logger.Info(fmt.Sprintf("Write struct message (SendMsgAndEndConversation) failed %s", err))
			return err
		}
	}

	err = conn.WriteStruct(global.ChatMessage{Message: "[end]", Date: now, Type: 0})
	if err != nil {
		conf.Logger.Info(fmt.Sprintf("Write struct message (SendMsgAndEndConversation end) failed %s", err))
		return err
	}
	return nil
}

func AiTranslate(prompt string) (string, error) {
	finalPrompt := fmt.Sprintf(`请把以下内容翻译成英文："%s"`, prompt)
	var msgArr []gogpt.ChatCompletionMessage
	apiKey := os.Getenv("API_KEY")
	if apiKey == "" {
		conf.Logger.Fatal("Missing API KEY")
	}
	c := gogpt.NewClient(apiKey)

	msgArr = append(msgArr, gogpt.ChatCompletionMessage{Role: "user", Content: finalPrompt})
	resp, err := c.CreateChatCompletion(
		context.Background(),
		gogpt.ChatCompletionRequest{
			Stream:   false,
			Messages: msgArr,
			Model:    gogpt.GPT3Dot5Turbo,
		})
	if err != nil {
		conf.Logger.Error("Failed to create Completion")
		return "", err
	}
	return resp.Choices[0].Message.Content, nil
}

func AiRequest(prompt string, u *model.User, conn *impl.Connection, isStream bool) error {
	var now = time.Now().Format("2006-01-02 15:04:05")
	var ctx context.Context

	apiKey := os.Getenv("API_KEY")
	if apiKey == "" {
		conf.Logger.Fatal("Missing API KEY")
	}
	c := gogpt.NewClient(apiKey)

	//ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
	//defer cancel()
	ctx = context.Background()
	// chat completion
	var messages []gogpt.ChatCompletionMessage
	var historyPrompts string
	var maxTokens = 800
	var historyLimit = 3
	var md = gogpt.GPT3Dot5Turbo
	var err error
	// retry配置
	var retryOpts = []retry.Option{
		retry.Context(ctx),                      // 支持传入带超时限定的ctx，如果ctx超时，但还在重试过程中，则直接返回ctx超时错误 基于select case实现）
		retry.Attempts(3),                       // 尝试重试的次数
		retry.Delay(800 * time.Millisecond),     // Delay结合MaxDelay、以及退避算法综合得到 （基于select case实现）
		retry.MaxDelay(1200 * time.Millisecond), // 重试最大间隔时间，如果退避后的间隔时间超过maxDelay，则改用maxDelay
		retry.LastErrorOnly(true),               // 仅返回最后一次错误
		retry.OnRetry(func(n uint, err error) { // 每次重试的时候调用方法
			conf.Logger.Error(fmt.Sprintf("[%s], retry #%d, because got err: %s", time.Now(), n, err))
		}),
		retry.DelayType(retry.CombineDelay(retry.BackOffDelay, retry.RandomDelay)), // 这个就是默认组合: 退避+随机抖动延迟(maxJitter)
		retry.RetryIf(func(err error) bool {
			if strings.Contains(err.Error(), "i/o timeout") ||
				strings.Contains(err.Error(), "connection reset by peer") ||
				strings.Contains(err.Error(), "no such host") ||
				strings.Contains(err.Error(), "TLS handshake timeout") {
				return true
			}
			return false
		}),
	}

	if u.IsVip {
		conf.Logger.Info(fmt.Sprintf("VIP enabled and setting arguments for user %d", u.Id))
		md = gogpt.GPT3Dot5Turbo
		maxTokens = 1200
		historyLimit = 6
	}
	if u.Gpt4Enabled {
		conf.Logger.Info(fmt.Sprintf("GPT4 enabled and setting arguments for user %d", u.Id))
		md = gogpt.GPT4
		maxTokens = 2000
		historyLimit = 2
	}

	conf.Logger.Info(fmt.Sprintf("User id %d, openid %s stream mode %v", u.Id, u.Openid, isStream))
	if strings.HasPrefix(prompt, "画") ||
		strings.Contains(prompt, "画个") ||
		strings.Contains(prompt, "画出") ||
		strings.Contains(prompt, "画幅") ||
		strings.Contains(prompt, "画一") ||
		strings.Contains(prompt, "画张") {
		var image gogpt.ImageResponse
		var err error
		var promptEng string

		var req = gogpt.ImageRequest{
			Prompt:         prompt,
			N:              1,
			ResponseFormat: gogpt.CreateImageResponseFormatB64JSON,
			Size:           gogpt.CreateImageSize256x256,
		}

		if u.IsVip {
			if u.StableDiffusionEnabled {
				var baseURL string
				conf.Logger.Info(fmt.Sprintf("StableDiffusionEnabled enabled"))
				promptEng, err = AiTranslate(prompt)
				if err != nil {
					conf.Logger.Error(fmt.Sprintf("AiTranslate failed: %v", err))
					return err
				}
				conf.Logger.Info(fmt.Sprintf("AiTranslate result: %v", promptEng))
				baseURL = "http://qzpfcolbmryd2fn3xn3crvvmhtx2fowkwaogko3erwygie6nmlwq.remote.moe"
				setting, has := model.GetSetting("baseURL")
				conf.Logger.Info(fmt.Sprintf("Setting = %v, has = %v", setting, has))
				if has {
					conf.Logger.Info("Setting base URL to " + setting.Value)
					baseURL = setting.Value
				}
				cApi := api.New(api.Config{
					BaseURL: baseURL,
				})

				negativePrompt := `monochrome ,lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, lowres, bad anatomy, bad hands, text, error, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, ugly,,vore,duplicate,morbid,mut ilated,tran nsexual,long neck,mutated hands,poorly drawn hands,poorly drawn face,mutation,deformed,blurry,bad anatomy,bad proportions,malformed limbs,extra limbs,cloned face,disfigured,gross proportions, (((missing arms))),((( missing legs))), (((extra arms))),(((extra legs))),pubic hair, plump,bad legs,error legs,username,blurry,bad feet, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry`

				resp, err1 := cApi.Text2Image(&api.Txt2Image{
					Prompt:         promptEng,
					NegativePrompt: negativePrompt,
					Width:          256,
					Height:         256,
				})
				conf.Logger.Info(fmt.Sprintf("text2image returned response %v", resp))
				conf.Logger.Info(fmt.Sprintf("text2image returned response %v", resp))
				if err1 != nil {
					conf.Logger.Error(fmt.Sprintf("Text2Image failed: %v", err1))

					err = retry.Do(func() error {
						image, err = c.CreateImage(ctx, req)
						if err != nil {
							conf.Logger.Error(fmt.Sprintf("CreateImage failed %s", err))
							return err
						}
						return nil
					}, retryOpts...)
					if err != nil {
						conf.Logger.Error(fmt.Sprintf("CreateImage got err: %v", err))
						if err1 := SendMsgAndEndConversation(conn, fmt.Sprintf("错误：%s", err)); err1 != nil {
							conf.Logger.Info(fmt.Sprintf("CreateImage[retry] failed %s", err))
						}
						return nil
					}
					conf.Logger.Info(fmt.Sprintf("CreateImage succeeded, length %d", len(image.Data[0].B64JSON)))
					fullContent := "data:image/jpg;base64," + image.Data[0].B64JSON
					err = conn.WriteStruct(global.ChatMessage{Message: fullContent, Date: now, Type: 0, IsImage: "true"})
					if err != nil {
						conf.Logger.Info(fmt.Sprintf("Write struct message (EOF) failed %s", err))
						return err
					}
				}

				if len(resp.Images) > 0 {
					conf.Logger.Info(fmt.Sprintf("CreateImage(Stable diffusion) succeeded, length %d", len(resp.Images[0])))
					fullContent := "data:image/jpg;base64," + resp.Images[0]
					err = conn.WriteStruct(global.ChatMessage{Message: fullContent, Date: now, Type: 0, IsImage: "true"})
					if err != nil {
						conf.Logger.Info(fmt.Sprintf("Write struct message (EOF) failed %s", err))
						return err
					}
					if err := DeductBalanceSaveText(u, prompt, fullContent); err != nil {
						conf.Logger.Error(fmt.Sprintf("DeductBalanceSaveText failed: %v", err))
						return err
					}
				} else {
					conf.Logger.Error(fmt.Sprintf("Response empty"))
					conf.Logger.Info(fmt.Sprintf("Response DecodedImages %s", resp.DecodedImages))
					conf.Logger.Info(fmt.Sprintf("Response Info %s", resp.Info))
					allImgs, err := resp.DecodeAllImages()
					if err != nil {
						conf.Logger.Error(fmt.Sprintf("Error decoding images: %v", err))
					}
					conf.Logger.Info(fmt.Sprintf("Response allImgs %v", allImgs))
				}
			} else {
				conf.Logger.Info(fmt.Sprintf("StableDiffusionEnabled disabled"))
				err = retry.Do(func() error {
					image, err = c.CreateImage(ctx, req)
					if err != nil {
						conf.Logger.Error(fmt.Sprintf("CreateImage failed %s", err))
						return err
					}
					return nil
				}, retryOpts...)
				if err != nil {
					conf.Logger.Error(fmt.Sprintf("CreateImage got err: %v", err))
					if err1 := SendMsgAndEndConversation(conn, fmt.Sprintf("错误：%s", err)); err1 != nil {
						conf.Logger.Info(fmt.Sprintf("CreateImage[retry] failed %s", err))
					}
					return nil
				}
				conf.Logger.Error(fmt.Sprintf("CreateImage succeeded, length %d", len(image.Data[0].B64JSON)))
				fullContent := "data:image/jpg;base64," + image.Data[0].B64JSON
				err = conn.WriteStruct(global.ChatMessage{Message: fullContent, Date: now, Type: 0, IsImage: "true"})
				if err != nil {
					conf.Logger.Info(fmt.Sprintf("Write struct message (EOF) failed %s", err))
					return err
				}
				if err := DeductBalanceSaveText(u, prompt, fullContent); err != nil {
					conf.Logger.Error(fmt.Sprintf("DeductBalanceSaveText failed: %v", err))
					return err
				}
			}

			return nil
		} else {
			err := SendMsgAndEndConversation(conn, fmt.Sprintf("抱歉！画图功能仅限内测用户使用！"))
			if err != nil {
				conf.Logger.Info(fmt.Sprintf("SendMsgAndEndConversation not vip failed %s", err))
			}
		}
		return nil

	} else {
		if historyLimit > 0 {
			conf.Logger.Info(fmt.Sprintf("Set history for conversation for user %d, limit = %d", u.Id, historyLimit))
			cvs, err := model.ConversationAll(u.Id, historyLimit)
			if err != nil {
				conf.Logger.Error(fmt.Sprintf("Get user conversation failed %s", err))
			}

			messages = append(messages, gogpt.ChatCompletionMessage{Role: "system", Content: "You are a helpful assistant."})
			for idx, c := range cvs {
				historyPrompts += fmt.Sprintf("历史提问%d：%s ", idx+1, c.Prompt)
				messages = append(messages, gogpt.ChatCompletionMessage{Role: "user", Content: c.Prompt})
				messages = append(messages, gogpt.ChatCompletionMessage{Role: "assistant", Content: c.Completion})

			}
		}
		messages = append(messages, gogpt.ChatCompletionMessage{Role: "user", Content: prompt})

		conf.Logger.Info(fmt.Sprintf("History conversation for user %s is %s", u.Openid, historyPrompts))

		req := gogpt.ChatCompletionRequest{
			//Model:     gogpt.GPT3Dot5Turbo0301,
			Model:     md,
			Messages:  messages,
			MaxTokens: maxTokens,
			Stream:    isStream,
		}

		if isStream {
			var stream *gogpt.ChatCompletionStream
			err := retry.Do(func() error {
				stream, err = c.CreateChatCompletionStream(ctx, req)
				if err != nil {
					fmt.Printf("CompletionStream error: %v\n", err)
					return err
				}
				return nil
			}, retryOpts...)
			if err != nil {
				conf.Logger.Error(fmt.Sprintf("Error creating chat completion stream: %v", err))
				if err1 := SendMsgAndEndConversation(conn, fmt.Sprintf("错误：%s", err)); err1 != nil {
					conf.Logger.Info(fmt.Sprintf("CreateChatCompletionStream[stream] failed %s", err))
				}
				return nil
			}
			defer stream.Close()
			var text string
			for {
				response, err := stream.Recv()
				if errors.Is(err, io.EOF) {
					conf.Logger.Info(fmt.Sprintf("Stream recv finished, full text is %s", text))
					// Write
					if len(strings.TrimSpace(text)) == 0 {
						if err := SendMsgAndEndConversation(conn, "AI无响应，请重新提问！"); err != nil {
							conf.Logger.Info(fmt.Sprintf("SendMsgAndEndConversation[eof] failed %s", err))
						}
					} else {
						if err := DeductBalanceSaveText(u, prompt, text); err != nil {
							conf.Logger.Error(fmt.Sprintf("DeductBalanceSaveText failed: %v", err))
							return err
						}
						if err := SendMsgAndEndConversation(conn, ""); err != nil {
							conf.Logger.Info(fmt.Sprintf("SendMsgAndEndConversation[eof] failed %s", err))
						}
					}
					return nil
				}

				if err != nil {
					conf.Logger.Error(fmt.Sprintf("Stream error: %v\n", err))
					if err := SendMsgAndEndConversation(conn, fmt.Sprintf("Stream error: %v", err)); err != nil {
						conf.Logger.Info(fmt.Sprintf("SendMsgAndEndConversation[Stream error] failed %s", err))
						return err
					}
					return err
				}
				if len(response.Choices) > 0 {
					err = conn.WriteStruct(global.ChatMessage{Message: response.Choices[0].Delta.Content, Date: now, Type: 0})
					if err != nil {
						conf.Logger.Info(fmt.Sprintf("Write struct message (Delta.Content) failed %s", err))
						return err
					}
					text += response.Choices[0].Delta.Content
				} else {
					if err := SendMsgAndEndConversation(conn, fmt.Sprintf("AI响应数据为空，请重新提问！")); err != nil {
						conf.Logger.Info(fmt.Sprintf("SendMsgAndEndConversation[Choices] failed %s", err))
						return err
					}
					return nil
				}
			}

		} else {
			response, err := c.CreateChatCompletion(ctx, req)
			if err != nil {
				conf.Logger.Error(fmt.Sprintf("Create chat completion failed %s", err))
				return err
			}
			conf.Logger.Info(fmt.Sprintf("%d total tokens are used", response.Usage.TotalTokens))

			var text = response.Choices[0].Message.Content
			conf.Logger.Info(fmt.Sprintf("response text is %s", text))
			if err := DeductBalanceSaveText(u, prompt, text); err != nil {
				conf.Logger.Error(fmt.Sprintf("DeductBalanceSaveText failed: %v", err))
				return err
			}

			err = conn.WriteStruct(global.ChatMessage{Message: strings.TrimSpace(text), Date: now, Type: 0})
			if err != nil {
				conf.Logger.Info(fmt.Sprintf("Write struct message (responseText) failed %s", err))
				conn.Close()
				return err
			}
			return nil
		}
	}
	return nil
}

func getOpenID(code string) (string, error) {
	openid, err := sendWxAuthAPI(code)
	if err != nil {
		return openid, err
	}
	//conf.Logger.Info("my openid", openid)
	return openid, nil

}

func sendWxAuthAPI(code string) (string, error) {
	var AppID = os.Getenv("APP_ID")
	var AppSecret = os.Getenv("APP_SECRET")
	url := fmt.Sprintf(code2sessionURL, AppID, AppSecret, code)
	resp, err := http.DefaultClient.Get(url)
	if err != nil {
		conf.Logger.Error(fmt.Sprintf("Http request failed: %v", err))
		return "", err
	}

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		conf.Logger.Error(fmt.Sprintf("Read body failed %v", err))
	}
	//conf.Logger.Info("body => ", string(bodyBytes))

	var wxMap map[string]interface{}
	err = json.Unmarshal(bodyBytes, &wxMap)
	if err != nil {
		conf.Logger.Error(fmt.Sprintf("Unmarshal body failed %v", err))
		return "", err
	}

	defer resp.Body.Close()
	if _, ok := wxMap["errcode"]; ok {
		errMsg := wxMap["errmsg"].(string)
		conf.Logger.Error("Wechat Error: ", errMsg)
		return errMsg, errors.New(errMsg)
	}

	return wxMap["openid"].(string), nil
}
